Skip to content

sec(api): add /.well-known/security.txt + /security.txt (BUG-API-411)#183

Merged
mastermanas805 merged 5 commits into
masterfrom
fix/api-security-txt-2026-05-30
May 30, 2026
Merged

sec(api): add /.well-known/security.txt + /security.txt (BUG-API-411)#183
mastermanas805 merged 5 commits into
masterfrom
fix/api-security-txt-2026-05-30

Conversation

@mastermanas805
Copy link
Copy Markdown
Member

Summary

RFC 9116 — pre-fix /.well-known/security.txt AND the apex /security.txt fallback both returned 404, leaving security researchers no documented disclosure contact. New handler serves a validating body from both paths with two Contact channels (mailto: + https://), 1-year Expires window auto-refreshed each redeploy, Preferred-Languages, Canonical, and Policy.

Coverage block (rule 17)

Field Value
Symptom researchers hit /.well-known/security.txt and got a 404 envelope with no disclosure path
Enumeration rg -nF 'security.txt' internal/ — 2 emit sites, 1 shared closure
Sites found 2 (/.well-known/security.txt + /security.txt)
Sites touched both paths share a closure; the test asserts byte-identical bodies across both
Coverage test TestSecurityTxt_ServedFromBothPathsWithRFC9116Body — sub-test per path, every RFC-mandatory + recommended field present, Expires parses + future + ≤2y
Live verified pending merge: curl -sS https://api.instanode.dev/.well-known/security.txt + curl -sS https://api.instanode.dev/security.txt

Verification

Local gate:

  • go build ./... clean
  • go vet ./... clean
  • golangci-lint run ./internal/router/... 0 issues
  • go test ./internal/router/ green
  • go test ./internal/middleware/ ./internal/migrations/ green

Test plan

  • Local gate green
  • CI green
  • Live: both paths return 200 text/plain with the RFC 9116 body
  • Live: securitytxt.org parser accepts the body

mastermanas805 and others added 5 commits May 30, 2026 09:38
RFC 9116 — security researchers reach for /.well-known/security.txt to
find a responsible-disclosure contact before filing a public
vulnerability report. Pre-fix both api and apex returned 404 for the
.well-known canonical path AND the /security.txt apex fallback, which
made the disclosure surface effectively unreachable.

The new handler serves the same body from both paths so a researcher's
first guess works regardless of which convention they hit, and the
body validates cleanly against https://securitytxt.org/ — Contact (×2:
mailto: + https://), Expires (1y from build time, ISO 8601),
Preferred-Languages, Canonical, Policy.

Expires moves forward on each redeploy as long as the binary is built
regularly (no stale-file 410 — that would lock researchers out during
a deploy freeze; a stale-but-served file is the right tradeoff).

Coverage block (rule 17):

  Symptom:        researchers hit /.well-known/security.txt and got a
                  404 envelope with no disclosure contact path.
  Enumeration:    `rg -nF 'security.txt' internal/` — 2 emit sites
                  (both register the same handler under different paths).
  Sites found:    2 paths (.well-known + apex fallback), 1 shared
                  handler closure.
  Sites touched:  both paths covered. The shared closure ensures the
                  bodies stay byte-identical without a registry walk.
  Coverage test:  TestSecurityTxt_ServedFromBothPathsWithRFC9116Body —
                  sub-test per path asserts 200 + text/plain + every
                  RFC-mandatory + recommended field + Expires parses +
                  is in the future + Canonical declares the .well-known
                  path + bodies identical across both paths.
  Live verified:  pending post-merge SHA round-trip:
                    curl -sS https://api.instanode.dev/.well-known/security.txt
                    curl -sS https://api.instanode.dev/security.txt

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…h coverage

The CI patch-coverage gate (100% of changed lines) trips on the inline
closure inside router.New because the closure body is only reachable
via the full router-startup path (which needs Postgres + Redis + gRPC
and is intentionally not exercised from unit tests). Extract the
handler builder into its own file (security_txt.go) and expose it
under an _test.go-only alias so the existing
TestSecurityTxt_ServedFromBothPathsWithRFC9116Body covers every
changed line at 100%.

No behavioural change — router.New now calls makeSecurityTxtHandler
instead of inlining the same closure. Coverage rose from 91.4% (3 lines
missed) to 100% on the patched chunk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OpenAPI-route-coverage gate (TestOpenAPI_CoversAllRegisteredRoutes)
caught the new RFC 9116 security.txt routes. They are
security-researcher disclosure surfaces, not agent-facing APIs (the
body is hand-crafted text/plain, not JSON, so they have no OpenAPI
schema). Add both paths to intentionallyHidden with a comment
justifying the omission.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mastermanas805 mastermanas805 merged commit c667270 into master May 30, 2026
14 checks passed
@mastermanas805 mastermanas805 deleted the fix/api-security-txt-2026-05-30 branch May 30, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant